/**********************************************************************

Audacity: A Digital Audio Editor

ProjectManager.cpp

Paul Licameli split from AudacityProject.cpp

**********************************************************************/
#include "ProjectManager.h"

#include "ActiveProject.h"
#include "AdornedRulerPanel.h"
#include "AudioIO.h"
#include "Clipboard.h"
#include "FileNames.h"
#include "MenuCreator.h"
#include "ModuleManager.h"
#include "Project.h"
#include "ProjectAudioIO.h"
#include "ProjectAudioManager.h"
#include "ProjectFileIO.h"
#include "ProjectFileIOExtension.h"
#include "ProjectFileManager.h"
#include "ProjectHistory.h"
#include "ProjectRate.h"
#include "ProjectSettings.h"
#include "ProjectStatus.h"
#include "ProjectWindow.h"
#include "ProjectWindows.h"
#include "SelectUtilities.h"
#include "TrackPanel.h"
#include "TrackUtilities.h"
#include "UndoManager.h"
#include "Viewport.h"
#include "WaveTrack.h"
#include "wxFileNameWrapper.h"
#include "Import.h"
#include "QualitySettings.h"
#include "toolbars/MeterToolBar.h"
#include "toolbars/ToolManager.h"
#include "AudacityMessageBox.h"
#include "widgets/FileHistory.h"
#include "WindowAccessible.h"

#include <wx/app.h>
#include <wx/scrolbar.h>
#include <wx/sizer.h>
#include <wx/splitter.h>

#ifdef __WXGTK__
#include "../images/AudacityLogoAlpha.xpm"
#endif

namespace {
void SaveWindowPreferences(const wxRect& windowRect, const wxRect& normalRect,
                           bool isMaximized, bool isIconized)
{
    ProjectWindowX.Write(windowRect.GetX());
    ProjectWindowY.Write(windowRect.GetY());
    ProjectWindowWidth.Write(windowRect.GetWidth());
    ProjectWindowHeight.Write(windowRect.GetHeight());
    ProjectWindowMaximized.Write(isMaximized);
    ProjectWindowNormalX.Write(normalRect.GetX());
    ProjectWindowNormalY.Write(normalRect.GetY());
    ProjectWindowNormalWidth.Write(normalRect.GetWidth());
    ProjectWindowNormalHeight.Write(normalRect.GetHeight());
    ProjectWindowIconized.Write(isIconized);
}
}

const int AudacityProjectTimerID = 5200;

static AudacityProject::AttachedObjects::RegisteredFactory sProjectManagerKey {
    []( AudacityProject& project ) {
        return std::make_shared< ProjectManager >(project);
    }
};

ProjectManager& ProjectManager::Get(AudacityProject& project)
{
    return project.AttachedObjects::Get< ProjectManager >(sProjectManagerKey);
}

const ProjectManager& ProjectManager::Get(const AudacityProject& project)
{
    return Get(const_cast< AudacityProject& >(project));
}

ProjectManager::ProjectManager(AudacityProject& project)
    : mProject{project}
    , mTimer{std::make_unique<wxTimer>(this, AudacityProjectTimerID)}
{
    auto& window = ProjectWindow::Get(mProject);
    window.Bind(wxEVT_CLOSE_WINDOW, &ProjectManager::OnCloseWindow, this);
    mProjectStatusSubscription = ProjectStatus::Get(mProject)
                                 .Subscribe(*this, &ProjectManager::OnStatusChange);
    mProjectFileIOSubscription = ProjectFileIO::Get(mProject)
                                 .Subscribe(*this, &ProjectManager::OnReconnectionFailure);
}

ProjectManager::~ProjectManager() = default;

BEGIN_EVENT_TABLE(ProjectManager, wxEvtHandler)
EVT_TIMER(AudacityProjectTimerID, ProjectManager::OnTimer)
END_EVENT_TABLE()

bool ProjectManager::sbWindowRectAlreadySaved = false;
bool ProjectManager::sbSkipPromptingForSave = false;

void ProjectManager::SaveWindowSize()
{
    if (sbWindowRectAlreadySaved) {
        return;
    }
    bool validWindowForSaveWindowSize = FALSE;
    ProjectWindow* validProject = nullptr;
    bool foundIconizedProject = FALSE;
    for ( auto pProject : AllProjects{} ) {
        auto& window = ProjectWindow::Get(*pProject);
        if (!window.IsIconized()) {
            validWindowForSaveWindowSize = TRUE;
            validProject = &window;
            break;
        } else {
            foundIconizedProject =  TRUE;
        }
    }

    if (validWindowForSaveWindowSize) {
        wxRect windowRect = validProject->GetRect();
        wxRect normalRect = validProject->GetNormalizedWindowState();
        bool wndMaximized = validProject->IsMaximized();

        SaveWindowPreferences(windowRect, normalRect, wndMaximized, false);
    } else {
        if (foundIconizedProject) {
            validProject = &ProjectWindow::Get(**AllProjects {}.begin());
            bool wndMaximized = validProject->IsMaximized();
            wxRect normalRect = validProject->GetNormalizedWindowState();

            // store only the normal rectangle because the itemized rectangle
            // makes no sense for an opening project window
            SaveWindowPreferences(normalRect, normalRect, wndMaximized, true);
        } else {
            // this would be a very strange case that might possibly occur on the Mac
            // Audacity would have to be running with no projects open
            // in this case we are going to write only the default values
            wxRect defWndRect;
            GetDefaultWindowRect(&defWndRect);
            SaveWindowPreferences(defWndRect, defWndRect, false, false);
        }
    }
    sbWindowRectAlreadySaved = true;
}

void InitProjectWindow(ProjectWindow& window)
{
    auto pProject = window.FindProject();
    if (!pProject) {
        return;
    }
    auto& project = *pProject;
    auto& viewport = Viewport::Get(project);

    auto statusBar = window.CreateProjectStatusBar();

    auto& viewInfo = ViewInfo::Get(project);

    // LLL:  Read this!!!
    //
    // Until the time (and cpu) required to refresh the track panel is
    // reduced, leave the following window creations in the order specified.
    // This will place the refresh of the track panel last, allowing all
    // the others to get done quickly.
    //
    // Near as I can tell, this is only a problem under Windows.
    //

    //
    // Create the ToolDock
    //
    ToolManager::Get(project).CreateWindows();
    ToolManager::Get(project).LayoutToolBars();

    //
    // Create the TrackPanel and the scrollbars
    //

    auto topPanel = window.GetTopPanel();

    {
        auto ubs = std::make_unique<wxBoxSizer>(wxVERTICAL);
        ubs->Add(ToolManager::Get(project).GetTopDock(), 0, wxEXPAND | wxALIGN_TOP);
        topPanel->SetSizer(ubs.release());
    }

    wxBoxSizer* bs;
    {
        auto ubs = std::make_unique<wxBoxSizer>(wxVERTICAL);
        bs = ubs.get();
        bs->Add(topPanel, 0, wxEXPAND | wxALIGN_TOP);
        bs->Add(window.GetContainerWindow(), 1, wxEXPAND);
        bs->Add(ToolManager::Get(project).GetBotDock(), 0, wxEXPAND);
        window.SetAutoLayout(true);
        window.SetSizer(ubs.release());
    }
    bs->Layout();

    auto& trackPanel = TrackPanel::Get(project);

    // LLL: When Audacity starts or becomes active after returning from
    //      another application, the first window that can accept focus
    //      will be given the focus even if we try to SetFocus().  By
    //      making the TrackPanel that first window, we resolve several
    //      keyboard focus problems.
    window.GetContainerWindow()->MoveAfterInTabOrder(topPanel);

    const auto trackListWindow = window.GetTrackListWindow();

    //
    // Create the horizontal ruler
    //
    auto& ruler = AdornedRulerPanel::Get(project);

    bs = static_cast<wxBoxSizer*>(trackListWindow->GetSizer());

    auto vsBar = &window.GetVerticalScrollBar();
    auto hsBar = &window.GetHorizontalScrollBar();

    {
        // Top horizontal grouping
        auto hs = std::make_unique<wxBoxSizer>(wxHORIZONTAL);

        // Track panel
        hs->Add(&trackPanel, 1, wxEXPAND | wxALIGN_LEFT | wxALIGN_TOP);

        {
            // Vertical grouping
            auto vs = std::make_unique<wxBoxSizer>(wxVERTICAL);

            // Vertical scroll bar
            vs->Add(vsBar, 1, wxEXPAND | wxALIGN_TOP);
            hs->Add(vs.release(), 0, wxEXPAND | wxALIGN_TOP);
        }

        bs->Add(&ruler, 0, wxEXPAND | wxALIGN_TOP);
        bs->Add(hs.release(), 1, wxEXPAND | wxALIGN_LEFT | wxALIGN_TOP);
    }

    {
        // Bottom horizontal grouping
        auto hs = std::make_unique<wxBoxSizer>(wxHORIZONTAL);

        // Bottom scrollbar
        hs->Add(viewInfo.GetLeftOffset() - 1, 0);
        hs->Add(hsBar, 1, wxALIGN_BOTTOM);
        hs->Add(vsBar->GetSize().GetWidth(), 0);
        bs->Add(hs.release(), 0, wxEXPAND | wxALIGN_LEFT);
    }

    // Lay it out
    trackListWindow->SetAutoLayout(true);
    trackListWindow->Layout();

    wxASSERT(trackPanel.GetProject() == &project);

    // MM: Give track panel the focus to ensure keyboard commands work
    trackPanel.SetFocus();

    viewport.UpdateScrollbarsForTracks();
    ruler.SetLeftOffset(viewInfo.GetLeftOffset()); // bevel on AdornedRuler

    //
    // Set the Icon
    //

    // loads either the XPM or the windows resource, depending on the platform
#if !defined(__WXMAC__) && !defined(__WXX11__)
    {
#if defined(__WXMSW__)
        wxIcon ic{ wxICON(AudacityLogo) };
#elif defined(__WXGTK__)
        wxIcon ic{ wxICON(AudacityLogoAlpha) };
#else
        wxIcon ic{};
        ic.CopyFromBitmap(theTheme.Bitmap(bmpAudacityLogo48x48));
#endif
        window.SetIcon(ic);
    }
#endif

    window.UpdateStatusWidths();
    auto msg = XO("Welcome to Audacity version %s")
               .Format(AUDACITY_VERSION_STRING);
    ProjectManager::Get(project).SetStatusText(msg, MainStatusBarField());
}

AudacityProject* ProjectManager::New()
{
    wxRect wndRect;
    bool bMaximized = false;
    bool bIconized = false;
    GetNextWindowPlacement(&wndRect, &bMaximized, &bIconized);

    // Create and show a NEW project
    // Use a non-default deleter in the smart pointer!
    auto sp = AudacityProject::Create();
    AllProjects {}.Add(sp);
    auto p = sp.get();
    auto& project = *p;
    auto& projectHistory = ProjectHistory::Get(project);
    auto& projectManager = Get(project);
    auto& window = ProjectWindow::Get(*p);

    // Issue #2569
    // There is a dependency on the order of initialisation.
    // The menus must be created, and registered, before
    // InitProjectWindows can UpdateMenus.
    MenuCreator::Get(project).CreateMenusAndCommands();

    InitProjectWindow(window);

    // wxGTK3 seems to need to require creating the window using default position
    // and then manually positioning it.
    window.SetPosition(wndRect.GetPosition());

    auto& projectFileManager = ProjectFileManager::Get(*p);

    // This may report an error.
    projectFileManager.OpenNewProject();

    projectHistory.InitialState();
    projectManager.RestartTimer();

    if (bMaximized) {
        window.Maximize(true);
    } else if (bIconized) {
        // if the user close down and iconized state we could start back up and iconized state
        // window.Iconize(TRUE);
    }

    //Initialise the Listeners
    auto gAudioIO = AudioIO::Get();
    gAudioIO->SetListener(
        ProjectAudioManager::Get(project).shared_from_this());

    //Set the NEW project as active:
    SetActiveProject(p);

    // Okay, GetActiveProject() is ready. Now we can get its CommandManager,
    // and add the shortcut keys to the tooltips.
    ToolManager::Get(*p).RegenerateTooltips();

    ModuleManager::Get().Dispatch(ProjectInitialized);

    window.Show(true);

    return p;
}

void ProjectManager::OnReconnectionFailure(ProjectFileIOMessage message)
{
    if (message == ProjectFileIOMessage::ReconnectionFailure) {
        ProjectWindow::Get(mProject).Close(true);
    }
}

static bool sbClosingAll = false;

void ProjectManager::SetClosingAll(bool closing)
{
    sbClosingAll = closing;
}

void ProjectManager::OnCloseWindow(wxCloseEvent& event)
{
    auto& project = mProject;
    auto& projectFileIO = ProjectFileIO::Get(project);
    auto& projectFileManager = ProjectFileManager::Get(project);
    const auto& settings = ProjectSettings::Get(project);
    auto& projectAudioIO = ProjectAudioIO::Get(project);
    auto& tracks = TrackList::Get(project);
    auto& viewport = Viewport::Get(project);
    auto& window = ProjectWindow::Get(project);
    auto gAudioIO = AudioIO::Get();

    // We are called for the wxEVT_CLOSE_WINDOW, wxEVT_END_SESSION, and
    // wxEVT_QUERY_END_SESSION, so we have to protect against multiple
    // entries.  This is a hack until the whole application termination
    // process can be reviewed and reworked.  (See bug #964 for ways
    // to exercise the bug that instigated this hack.)
    if (window.IsBeingDeleted()) {
        event.Skip();
        return;
    }

    if (event.CanVeto() && (::wxIsBusy() || project.mbBusyImporting)) {
        event.Veto();
        return;
    }

    // Check to see if we were playing or recording
    // audio, and if so, make sure Audio I/O is completely finished.
    // The main point of this is to properly push the state
    // and flush the tracks once we've completely finished
    // recording NEW state.
    // This code is derived from similar code in
    // AudacityProject::~AudacityProject() and TrackPanel::OnTimer().
    if (projectAudioIO.GetAudioIOToken() > 0
        && gAudioIO->IsStreamActive(projectAudioIO.GetAudioIOToken())) {
        // We were playing or recording audio, but we've stopped the stream.
        ProjectAudioManager::Get(project).Stop();

        projectAudioIO.SetAudioIOToken(0);
        viewport.Redraw();
    } else if (gAudioIO->IsMonitoring()) {
        gAudioIO->StopStream();
    }

    // MY: Use routine here so other processes can make same check
    bool bHasTracks = !tracks.empty();

    // We may not bother to prompt the user to save, if the
    // project is now empty.
    if (!sbSkipPromptingForSave
        && event.CanVeto()
        && bHasTracks) {
        if (UndoManager::Get(project).UnsavedChanges()) {
            TitleRestorer Restorer(window, project);// RAII
            /* i18n-hint: The first %s numbers the project, the second %s is the project name.*/
            auto Title = XO("%sSave changes to %s?")
                         .Format(Restorer.sProjNumber, Restorer.sProjName);
            auto Message = XO("Save project before closing?");

            int result = AudacityMessageBox(
                Message,
                Title,
                wxYES_NO | wxCANCEL | wxICON_QUESTION,
                &window);

            if (result == wxCANCEL || (result == wxYES
                                       && !GuardedCall<bool>([&]{ return projectFileManager.Save(); })
                                       )) {
                event.Veto();
                return;
            }
        }
    }

    // Ask extensions if they allow the project to be closed
    if (ProjectFileIOExtensionRegistry::OnClose(mProject) == OnCloseAction::Veto
        && event.CanVeto()) {
        event.Veto();
        return;
    }

#ifdef __WXMAC__
    // Fix bug apparently introduced into 2.1.2 because of wxWidgets 3:
    // closing a project that was made full-screen (as by clicking the green dot
    // or command+/; not merely "maximized" as by clicking the title bar or
    // Zoom in the Window menu) leaves the screen black.
    // Fix it by un-full-screening.
    // (But is there a different way to do this? What do other applications do?
    //  I don't see full screen windows of Safari shrinking, but I do see
    //  momentary blackness.)
    window.ShowFullScreen(false);
#endif

    // This achieves auto save on close of project before other important
    // project state is destroyed
    window.Publish(ProjectWindowDestroyedMessage {});
    ModuleManager::Get().Dispatch(ProjectClosing);

    // Stop the timer since there's no need to update anything anymore
    mTimer.reset();

    // DMM: Save the size of the last window the user closes
    //
    // LL: Save before doing anything else to the window that might make
    //     its size change.
    SaveWindowSize();

    window.SetIsBeingDeleted();

    // Mac: we never quit as the result of a close.
    // Other systems: we quit only when the close is the result of an external
    // command (on Windows, those are taskbar closes, "X" box, Alt+F4, etc.)
    bool quitOnClose;
#ifdef __WXMAC__
    quitOnClose = false;
#else
    quitOnClose = !projectFileManager.GetMenuClose();
#endif

    // DanH: If we're definitely about to quit, clear the clipboard.
    auto& clipboard = Clipboard::Get();
    if ((AllProjects{}.size() == 1)
        && (quitOnClose || sbClosingAll)) {
        clipboard.Clear();
    } else {
        auto clipboardProject = clipboard.Project().lock();
        if (clipboardProject.get() == &mProject) {
            // Closing the project from which content was cut or copied.
            // For 3.0.0, clear the clipboard, because accessing clipboard contents
            // would depend on a database connection to the closing project, but
            // that connection should closed now so that the project file can be
            // freely moved.
            // Notes:
            // 1) maybe clipboard contents could be saved by migrating them to
            // another temporary database, but that extra effort is beyond the
            // scope of 3.0.0.
            // 2) strictly speaking this is necessary only when the clipboard
            // contains WaveTracks.
            clipboard.Clear();
        }
    }

    // JKC: For Win98 and Linux do not detach the menu bar.
    // We want wxWidgets to clean it up for us.
    // TODO: Is there a Mac issue here??
    // SetMenuBar(NULL);

    auto& undoManager = UndoManager::Get(project);
    constexpr auto doAutoSave = false;
    ProjectHistory::Get(project).SetStateTo(
        undoManager.GetSavedState(), doAutoSave);

    // Compact the project.
    projectFileManager.CompactProjectOnClose();

    // Set (or not) the bypass flag to indicate that deletes that would happen
    // during undoManager.ClearStates() below are not necessary. Must be called
    // between `CompactProjectOnClose()` and `undoManager.ClearStates()`.
    projectFileIO.SetBypass();

    // This can reduce reference counts of sample blocks in the project's
    // tracks.
    undoManager.ClearStates();

    // Delete all the tracks to free up memory
    tracks.Clear();

    // Some of the AdornedRulerPanel functions refer to the TrackPanel, so destroy this
    // before the TrackPanel is destroyed. This change was needed to stop Audacity
    // crashing when running with Jaws on Windows 10 1703.
    AdornedRulerPanel::Destroy(project);

    // Destroy the TrackPanel early so it's not around once we start
    // deleting things like tracks and such out from underneath it.
    // Check validity of mTrackPanel per bug 584 Comment 1.
    // Deeper fix is in the Import code, but this failsafes against crash.
    TrackPanel::Destroy(project);
    // Finalize the tool manager before the children since it needs
    // to save the state of the toolbars.
    ToolManager::Get(project).Destroy();

    window.DestroyChildren();

    // Close project only now, because TrackPanel might have been holding
    // some shared_ptr to WaveTracks keeping SampleBlocks alive.
    // We're all done with the project file, so close it now
    projectFileManager.CloseProject();

    WaveTrackFactory::Destroy(project);

    // Remove self from the global array, but defer destruction of self
    auto pSelf = AllProjects{}.Remove(project);
    wxASSERT(pSelf);

    if (GetActiveProject().lock().get() == &project) {
        // Find a NEW active project
        if (!AllProjects{}.empty()) {
            SetActiveProject(AllProjects {}.begin()->get());
        } else {
            SetActiveProject(nullptr);
        }
    }

    // Since we're going to be destroyed, make sure we're not to
    // receive audio notifications anymore.
    // PRL:  Maybe all this is unnecessary now that the listener is managed
    // by a weak pointer.
    if (gAudioIO->GetListener().get() == &ProjectAudioManager::Get(project)) {
        auto active = GetActiveProject().lock();
        gAudioIO->SetListener(
            active
            ? ProjectAudioManager::Get(*active).shared_from_this()
            : nullptr
            );
    }

    if (AllProjects{}.empty() && !sbClosingAll) {
#if !defined(__WXMAC__)
        if (quitOnClose) {
            // Simulate the application Exit menu item
            wxCommandEvent evt{ wxEVT_MENU, wxID_EXIT };
            wxTheApp->AddPendingEvent(evt);
        } else {
            sbWindowRectAlreadySaved = false;
            // For non-Mac, always keep at least one project window open
            (void)New();
        }
#endif
    }

    window.Destroy();

    // Destroys this
    pSelf.reset();
}

// static method, can be called outside of a project
void ProjectManager::OpenFiles(AudacityProject* proj)
{
    auto selectedFiles
        =ProjectFileManager::ShowOpenDialog(FileNames::Operation::Open);
    if (selectedFiles.size() == 0) {
        Importer::SetLastOpenType({});
        return;
    }

    //first sort selectedFiles.
    selectedFiles.Sort(FileNames::CompareNoCase);

    auto cleanup = finally([] {
        Importer::SetLastOpenType({});
    });

    for (const auto& fileName : selectedFiles) {
        // Make sure it isn't already open.
        if (ProjectFileManager::IsAlreadyOpen(fileName)) {
            continue; // Skip ones that are already open.
        }
        proj = OpenProject(proj, fileName,
                           true /* addtohistory */, false /* reuseNonemptyProject */);
    }
}

bool ProjectManager::SafeToOpenProjectInto(AudacityProject& proj)
{
    // DMM: If the project is dirty, that means it's been touched at
    // all, and it's not safe to open a fresh project directly in its
    // place.  Only if the project is brandnew clean and the user
    // hasn't done any action at all is it safe for Open to take place
    // inside the current project.
    //
    // If you try to Open a fresh project inside the current window when
    // there are no tracks, but there's an Undo history, etc, then
    // bad things can happen, including orphan blocks, or tracks
    // referring to non-existent blocks
    if (
        ProjectHistory::Get(proj).GetDirty()
        || !TrackList::Get(proj).empty()
        ) {
        return false;
    }
    // This project is clean; it's never been touched.  Therefore
    // all relevant member variables are in their initial state,
    // and it's okay to open a NEW project inside its window.
    return true;
}

ProjectManager::ProjectChooser::~ProjectChooser()
{
    if (mpUsedProject) {
        if (mpUsedProject == mpGivenProject) {
            // Ensure that it happens here: don't wait for the application level
            // exception handler, because the exception may be intercepted
            ProjectHistory::Get(*mpGivenProject).RollbackState();
            // Any exception now continues propagating
        } else {
            GetProjectFrame(*mpUsedProject).Close(true);
        }
    }
}

AudacityProject&
ProjectManager::ProjectChooser::operator()(bool openingProjectFile)
{
    if (mpGivenProject) {
        // Always check before opening a project file (for safety);
        // May check even when opening other files
        // (to preserve old behavior; as with the File > Open command specifying
        // multiple files of whatever types, so that each gets its own window)
        bool checkReuse = (openingProjectFile || !mReuseNonemptyProject);
        if (!checkReuse || SafeToOpenProjectInto(*mpGivenProject)) {
            return *(mpUsedProject = mpGivenProject);
        }
    }
    return *(mpUsedProject = New());
}

void ProjectManager::ProjectChooser::Commit()
{
    mpUsedProject = nullptr;
}

AudacityProject* ProjectManager::OpenProject(
    AudacityProject* pGivenProject, const FilePath& fileNameArg,
    bool addtohistory, bool reuseNonemptyProject)
{
    ProjectManager::ProjectChooser chooser{ pGivenProject, reuseNonemptyProject };
    if (auto pProject = ProjectFileManager::OpenFile(
            std::ref(chooser), fileNameArg, addtohistory)) {
        chooser.Commit();

        auto& projectFileIO = ProjectFileIO::Get(*pProject);
        if (projectFileIO.IsRecovered()) {
            auto& viewport = Viewport::Get(*pProject);
            viewport.Zoom(viewport.GetZoomOfToFit());
            // "Project was recovered" replaces "Create new project" in Undo History.
            auto& undoManager = UndoManager::Get(*pProject);
            undoManager.RemoveStates(0, 1);
        }
        return pProject;
    }
    return nullptr;
}

// This is done to empty out the tracks, but without creating a new project.
void ProjectManager::ResetProjectToEmpty()
{
    auto& project = mProject;
    auto& projectFileIO = ProjectFileIO::Get(project);
    auto& projectFileManager = ProjectFileManager::Get(project);
    auto& projectHistory = ProjectHistory::Get(project);
    auto& viewInfo = ViewInfo::Get(project);

    SelectUtilities::DoSelectAll(project);
    TrackUtilities::DoRemoveTracks(project);

    WaveTrackFactory::Reset(project);

    // InitialState will reset UndoManager
    projectHistory.InitialState();
    projectHistory.SetDirty(false);

    projectFileManager.CloseProject();
    projectFileManager.OpenProject();
}

void ProjectManager::RestartTimer()
{
    if (mTimer) {
        // mTimer->Stop(); // not really needed
        mTimer->Start(3000); // Update messages as needed once every 3 s.
    }
}

void ProjectManager::OnTimer(wxTimerEvent& WXUNUSED(event))
{
    auto& project = mProject;
    auto& projectAudioIO = ProjectAudioIO::Get(project);
    auto meterToolBars = MeterToolBar::GetToolBars(project);

    for (auto& meterToolBar : meterToolBars) {
        meterToolBar.get().UpdateControls();
    }

    auto gAudioIO = AudioIO::Get();
    // gAudioIO->GetNumCaptureChannels() should only be positive
    // when we are recording.
    if (projectAudioIO.GetAudioIOToken() > 0 && gAudioIO->GetNumCaptureChannels() > 0) {
        wxLongLong freeSpace = ProjectFileIO::Get(project).GetFreeDiskSpace();
        if (freeSpace >= 0) {
            int iRecordingMins = GetEstimatedRecordingMinsLeftOnDisk(gAudioIO->GetNumCaptureChannels());
            auto sMessage = XO("Disk space remaining for recording: %s")
                            .Format(GetHoursMinsString(iRecordingMins));

            // Do not change mLastMainStatusMessage
            SetStatusText(sMessage, MainStatusBarField());
        }
    }

    // As also with the TrackPanel timer:  wxTimer may be unreliable without
    // some restarts
    RestartTimer();
}

void ProjectManager::OnStatusChange(StatusBarField field)
{
    auto& project = mProject;

    // Be careful to null-check the window.  We might get to this function
    // during shut-down, but a timer hasn't been told to stop sending its
    // messages yet.
    auto pWindow = ProjectWindow::Find(&project);
    if (!pWindow) {
        return;
    }
    auto& window = *pWindow;

    window.UpdateStatusWidths();

    const auto& msg = ProjectStatus::Get(project).Get(field);
    SetStatusText(msg, field);

    if (field == MainStatusBarField()) {
        // When recording, let the NEW status message stay at least as long as
        // the timer interval (if it is not replaced again by this function),
        // before replacing it with the message about remaining disk capacity.
        RestartTimer();
    }
}

void ProjectManager::SetStatusText(
    const TranslatableString& text, const StatusBarField& field)
{
    const auto index = ProjectStatusFieldsRegistry::GetFieldIndex(mProject, field);

    if (index >= 0) {
        SetStatusText(text, index);
    }
}

void ProjectManager::SetStatusText(const TranslatableString& text, int number)
{
    auto& project = mProject;
    auto pWindow = ProjectWindow::Find(&project);
    if (!pWindow) {
        return;
    }
    auto& window = *pWindow;
    window.GetStatusBar()->SetStatusText(text.Translation(), number);
}

TranslatableString ProjectManager::GetHoursMinsString(int iMinutes)
{
    if (iMinutes < 1) {
        // Less than a minute...
        return XO("Less than 1 minute");
    }

    // Calculate
    int iHours = iMinutes / 60;
    int iMins = iMinutes % 60;

    auto sHours = XP("%d hour", "%d hours", 0)(iHours);

    auto sMins = XP("%d minute", "%d minutes", 0)(iMins);

    /* i18n-hint: A time in hours and minutes. Only translate the "and". */
    return XO("%s and %s.").Format(sHours, sMins);
}

// This routine will give an estimate of how many
// minutes of recording time we have available.
// The calculations made are based on the user's current
// preferences.
int ProjectManager::GetEstimatedRecordingMinsLeftOnDisk(long lCaptureChannels)
{
    auto& project = mProject;

    // Obtain the current settings
    auto oCaptureFormat = QualitySettings::SampleFormatChoice();
    if (lCaptureChannels == 0) {
        lCaptureChannels = AudioIORecordChannels.Read();
    }

    // Find out how much free space we have on disk
    wxLongLong lFreeSpace = ProjectFileIO::Get(project).GetFreeDiskSpace();
    if (lFreeSpace < 0) {
        return 0;
    }

    // Calculate the remaining time
    double dRecTime = 0.0;
    double bytesOnDiskPerSample = SAMPLE_SIZE_DISK(oCaptureFormat);
    dRecTime = lFreeSpace.GetHi() * 4294967296.0 + lFreeSpace.GetLo();
    dRecTime /= bytesOnDiskPerSample;
    dRecTime /= lCaptureChannels;
    dRecTime /= ProjectRate::Get(project).GetRate();

    // Convert to minutes before returning
    int iRecMins = (int)round(dRecTime / 60.0);
    return iRecMins;
}
